#include <stdio.h>
#include <stdlib.h>
//#include <sys/socket.h>
#include <arpa/inet.h>   //in6_addr, in_addr
#include <netinet/in.h>
#include <stdbool.h>
#include <strings.h> // bzero()
#include <string.h> // memcpy()

#include <asm/types.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>

#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netdb.h>

#ifndef UINT32
#define UINT32 unsigned long
#endif

#ifndef	UINT
#define UINT	unsigned int
#endif

#ifndef U_CHAR
#define	U_CHAR	unsigned char
#endif

struct nList{
	union {
		struct in_addr * node_v4;
		struct in6_addr * node_v6;
	};
	union {
		struct in_addr * gw_v4;
		struct in6_addr * gw_v6;
	};
	UINT	metric;
	UINT	netmask;
	struct nList * next;
};

struct nList * nListHeader;
struct nList * nListTail;

struct oneHopList{
	union {
		struct in_addr * node_v4;
		struct in6_addr	* node_v6;
	};
	struct oneHopList * next;
};

struct oneHopList * oneHopListHeader;
struct oneHopList * oneHopListTail;

struct route_info {
	U_CHAR		version_flag;
	struct in6_addr	* dst_node_v6;
	struct in_addr	* dst_node_v4;
	struct in6_addr	* next_hop_v6;
	struct in_addr	* next_hop_v4;
	bool		is_connected;
	UINT		metric;
	U_CHAR		channel_id;
	U_CHAR		error_id;	
	struct oneHopList * oneHopNeigh;	//neighbor list
	char *	whereHNA;
};

struct req_t
{
struct nlmsghdr msg;
struct rtmsg    rt;
char            buf[1024];
} req;

#define	NO_ERROR		0
#define	INVALID_VER		1
#define	SOCKET_ERR		2
#define	SENDING_ERR		3
#define	RECEIVING_ERR	4

#define	IS_V6	0
#define	IS_V4	1
#define	IS_V4V6	2

#define	IPV4_LEN	32
#define	IPV6_LEN	128

#define	IPV4_NETMASK		24 // 32, 24,16,8
#define	IPV4_SUBMASK		IPV4_NETMASK/8

#define	IPV6_NETMASK		64 // 128, 112, 64, ...
#define	IPV6_SUBMASK		IPV6_NETMASK/8

#define	NOT_FOUND			0
#define	FOUND_HOST			1
#define	FOUND_SUBNET		2

#define	B_SIZE				4096

int		descriptor;
UINT32 	pid;
UINT32	seq;
char rBuffer[8192];
char * rPtr;
int 	nlmsg_len;
struct nlmsghdr* msg;

void mem_free()
{
	struct nList * temp;
	struct nList * buf;
	temp = nListHeader;

	while(temp != NULL) 
	{
		buf = temp->next;
		free(temp);
		temp = buf;
	} 
}

void one_hop_free(struct oneHopList * header)
{
	struct oneHopList * temp;
	struct oneHopList * buf;
	temp = oneHopListHeader;

	while(temp != NULL) 
	{
		buf = temp->next;
		free(temp);
		temp = buf;
	} 

}


void prtList(U_CHAR versionFlag)
{

	struct nList * temp;
	temp = nListHeader;

	char dst_str[128];
	bzero(dst_str, 128);

	printf("\n\n== linked list == \n");

	while(temp != NULL) {
		if(versionFlag == IS_V4)
		{
			inet_ntop(AF_INET, temp->node_v4, dst_str, 128);
			printf("dst_str = %s\t", dst_str);
			inet_ntop(AF_INET, temp->gw_v4, dst_str, 128);
			printf("gw = %s\t", dst_str);
		} else
		{
			inet_ntop(AF_INET6, temp->node_v6, dst_str, 128);
			printf("dst_str = %s\t", dst_str);
			inet_ntop(AF_INET6, temp->gw_v6, dst_str, 128);
			printf("gw = %s\t", dst_str);
		}
		printf("metric = %d\t", temp->metric);
		printf("netmask = %d\n", temp->netmask);
		temp = temp->next;
	} 
}

void prtOneHopList(U_CHAR versionFlag)
{
	struct oneHopList * temp;
	temp = oneHopListHeader;

	char dst_str[128];
	bzero(dst_str, 128);

	printf("\n\n== one-hop neighbors == \n");

	while(temp != NULL) {
		if(versionFlag == IS_V4)
		{
			inet_ntop(AF_INET, temp->node_v4, dst_str, 128);
			printf("dst_str = %s\n", dst_str);
		} else
		{
			inet_ntop(AF_INET6, temp->node_v6, dst_str, 128);
			printf("dst_str = %s\n", dst_str);
		} 
		temp = temp->next;
	}
}

bool addOneHopList(void * nodeIP, U_CHAR flag)
{
	struct oneHopList * newNode;
	newNode = (struct oneHopList *)malloc(sizeof(struct oneHopList));

	if(flag == IS_V4) //ipv4 address
	{
		newNode->node_v4 = (struct in_addr *)malloc(sizeof(struct in_addr));
		memcpy(newNode->node_v4, nodeIP, IPV4_LEN/8); 
	} else if (flag == IS_V6)
	{
		newNode->node_v6 = (struct in6_addr *)malloc(sizeof(struct in6_addr));
		memcpy(newNode->node_v6, nodeIP, IPV6_LEN/8); 
	}

	if(!oneHopListHeader) // first item 
	{
		oneHopListHeader = oneHopListTail = newNode;
		newNode->next = NULL;
	} else
	{
		oneHopListTail->next = newNode;
		oneHopListTail = newNode;
		oneHopListTail->next = NULL;
	}
	return true;

}

bool addList(char * nodeIP, char * gwIP, UINT metric, UINT mask, U_CHAR flag)
{
	struct nList * newNode;
	newNode = (struct nList *)malloc(sizeof(struct nList));
	char dst_str[128];
	bzero(dst_str, 128);

/*
	inet_ntop(AF_INET, nodeIP, dst_str, 128);
	printf("dst_str = %s\t", dst_str);
	inet_ntop(AF_INET, gwIP, dst_str, 128);
	printf("gw = %s\t", dst_str);
	printf("metric = %d\t", metric);
	printf("netmask = %d\n", mask);
*/

	if(flag == IS_V4) //ipv4 address
	{
		newNode->node_v4 = (struct in_addr *)calloc(sizeof(char),sizeof(struct in_addr));
		memcpy(newNode->node_v4, nodeIP, IPV4_LEN/8); 
		newNode->gw_v4 = (struct in_addr *)calloc(1, sizeof(struct in_addr));
		memcpy(newNode->gw_v4, gwIP, IPV4_LEN/8); 
	} else if (flag == IS_V6)
	{
		newNode->node_v6 = (struct in6_addr *)malloc(sizeof(struct in6_addr));
		memcpy(newNode->node_v6, nodeIP, IPV6_LEN/8); 
		newNode->gw_v6 = (struct in6_addr *)malloc(sizeof(struct in6_addr));
		memcpy(newNode->gw_v6, gwIP, IPV6_LEN/8); 
	} else
	{
		printf("[ERROR] unknown version flag\n");
		return false;
	}
	newNode->metric = metric;
	newNode->netmask = mask;

	if(!nListHeader) // first item 
	{
		nListHeader = nListTail = newNode;
		newNode->next = NULL;
	} else
	{
		nListTail->next = newNode;
		nListTail = newNode;
		nListTail->next = NULL;
	}
	return true;
}


// Allocate memory for IP adress region
void init_rtInfo(struct route_info * rtInfo)
{
	rtInfo->next_hop_v6 = (struct in6_addr *) malloc(sizeof(struct in6_addr));
	rtInfo->next_hop_v4 = (struct in_addr *) malloc(sizeof(struct in_addr));
	switch (rtInfo->version_flag) {
		case IS_V6:
			rtInfo->dst_node_v4 = (struct in_addr *) malloc(sizeof(struct in_addr));
			break;	
		case IS_V4:
			rtInfo->dst_node_v6 = (struct in6_addr *) malloc(sizeof(struct in6_addr));
			break;
	}	
	rtInfo->is_connected = false;
	rtInfo->metric = 0;
} //end init_rtInfo()

int send_route_req(U_CHAR version_flag)
{
	pid = (UINT32)getpid();
	seq = (UINT32)getppid();
	UINT	prefixLen;

    if ((descriptor = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) < 0)
    {
        printf("socket creation error.\n");
        return NOT_FOUND;
    }

	// netlink message header
	req.msg.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
	req.msg.nlmsg_type = RTM_GETROUTE;
	//req.msg.nlmsg_flags = NLM_F_REQUEST | NLM_F_MATCH;
	req.msg.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
	req.msg.nlmsg_seq = seq;
	req.msg.nlmsg_pid = 0;

	switch(version_flag) {
		case(IS_V4):
			req.rt.rtm_family = AF_INET;
			prefixLen = IPV4_LEN;
			break;
		case(IS_V6):
			req.rt.rtm_family = AF_INET6;
			prefixLen = IPV6_LEN;
			break;
		default:
			return NOT_FOUND; //ERROR
			
	}

	req.rt.rtm_dst_len = prefixLen;
	req.rt.rtm_src_len = 0;
	//req.rt.rtm_table = RT_TABLE_UNSPEC;
	req.rt.rtm_table = RT_TABLE_MAIN;
	req.rt.rtm_protocol = RTPROT_UNSPEC;
	req.rt.rtm_scope = RT_SCOPE_UNIVERSE;
	req.rt.rtm_type = RTN_UNSPEC;

	req.rt.rtm_flags = 0;

	// write message to our netlink socket
	int result = send(descriptor, &req, sizeof(req), 0);
	if (result < 0)
	{
		printf("send() error.\n");
		return NOT_FOUND; //ERROR
	}
	return 0;
}

int read_route_req()
{

	bzero(rBuffer, 8192);
	rPtr = rBuffer;

	nlmsg_len = 0;
	while(1)
	{
		int msgLen = recv(descriptor, rPtr, B_SIZE, 0);
		if (msgLen < 0)
		{
			printf("recv() error.\n");
			return NOT_FOUND; //ERROR
		}
		msg = (struct nlmsghdr *) rPtr;

		if(msg->nlmsg_type == NLMSG_DONE) {
			break;
		}

		rPtr = rPtr + msgLen;
		nlmsg_len = nlmsg_len + msgLen;
	}
	return true;
}


void fill_route_req(U_CHAR version_flag)
{

	memset(&req, 0, sizeof(req));
	UINT	priority;
	UINT	prefixLen;
	char dst_temp[20];
	char gw_temp[20];
	char dst_str[128];

	bzero(dst_str, 128);

	// read response(s) until NLMSG_DONE

	msg = (struct nlmsghdr*)rBuffer;
	for (; 0 != NLMSG_OK(msg, (unsigned int)nlmsg_len); msg = NLMSG_NEXT(msg, nlmsg_len)) {
	//for (; 0 != NLMSG_OK(msg, (unsigned int)msgLen); msg = NLMSG_NEXT(msg, msgLen))
		//if ((msg->nlmsg_pid == (UINT32)pid) && (msg->nlmsg_seq == seq))
		if ((msg->nlmsg_seq == seq))
		{
			switch (msg->nlmsg_type)
			{
				case NLMSG_NOOP:
					break;
				case NLMSG_ERROR:
				{
					struct nlmsgerr* errorMsg = (struct nlmsgerr*)NLMSG_DATA(msg);
					printf("recvd NLMSG_ERROR error seq:%d code:%d...\n", msg->nlmsg_seq, errorMsg->error);
					break;
				}
				case NLMSG_OVERRUN:
					printf("recvd NLMSG_OVERRUN ...\n");
					break;
				case RTM_GETROUTE:
				{
					printf("recvd RTM_GETROUTE ...\n");
					break;
				}
				case RTM_NEWROUTE:
				{

					struct rtmsg* rtm = (struct rtmsg*)NLMSG_DATA(msg);
                    struct rtattr* rta = RTM_RTA(rtm);
                    int rtaLen = msg->nlmsg_len - NLMSG_LENGTH(sizeof(struct rtmsg));
					bzero(gw_temp, 20);
					bzero(dst_temp, 20);
					priority=0;
					prefixLen = 0;

					for (; RTA_OK(rta, rtaLen); rta = RTA_NEXT(rta, rtaLen))
                    {
                    	switch (rta->rta_type)
                        {
                        	case RTA_DST:  //destination
                            {
                               	prefixLen = rtm->rtm_dst_len;
								if(version_flag == IS_V6)
								{
									memcpy(dst_temp, RTA_DATA(rta), IPV6_LEN/8);
								} else if(version_flag == IS_V4)
								{
									memcpy(dst_temp, RTA_DATA(rta), IPV4_LEN/8);
								}
									
                                break;
							} //case RTA_DST

                            case RTA_GATEWAY:  //next hop
                            {
                               	prefixLen = rtm->rtm_dst_len;
								if(version_flag == IS_V6)
								{
									memcpy(gw_temp, RTA_DATA(rta), IPV6_LEN/8);
								} else if(version_flag == IS_V4)
								{	
									memcpy(gw_temp, RTA_DATA(rta), IPV4_LEN/8);
								}
                                break;
                            }

							case RTA_OIF:  //interface
                            {
                            	break;
                            }

                            case RTA_PRIORITY:  //metric
                            {
								//printf("priority = %d\n", *((int *) RTA_DATA(rta)));
                                memcpy(&priority, RTA_DATA(rta), 4);
                               	break;
                            }
						} //switch(rta->)
					} // for()

					if ((RTN_BROADCAST != rtm->rtm_type) &&
                       		(RTN_UNREACHABLE != rtm->rtm_type) &&
                               	(RTN_LOCAL != rtm->rtm_type) &&
                               		(0 == (RTM_F_CLONED & rtm->rtm_flags)))
                    {
						
						if(version_flag == IS_V4)
						{
                       		inet_ntop(AF_INET, dst_temp, dst_str, 128);
						//	printf("dst_str = %s\t", dst_str);
							inet_ntop(AF_INET, gw_temp, dst_str, 128);
						//	printf("gw = %s\t", dst_str);
						//	printf("strlen = %d\t", strlen(dst_str));
						//	printf("metric = %d\t", priority);
						//	printf("netmask = %d\n", prefixLen);
						}						
						
						addList(dst_temp, gw_temp, priority, prefixLen, version_flag);

					} //if ((RTN_BROADCAST != rtm->rtm_type)... 

				} //case RTM_NEWROUTE:

			} //switch (msg->...)
		}

	} //for()

}

int	resolve_string(char * text)
{

    struct addrinfo* addrInfoPtr = NULL;
    if(0 != getaddrinfo(text, NULL, NULL, &addrInfoPtr))
    {
        printf("ResolveFromString() getaddrinfo() error\n");
        return false;
    }
    if (addrInfoPtr->ai_family == PF_INET)
    {
        freeaddrinfo(addrInfoPtr);
		return IS_V4;
    }
    else if (addrInfoPtr->ai_family == PF_INET6)
    {
        freeaddrinfo(addrInfoPtr);
		return IS_V6;
    }
    else
    {
        freeaddrinfo(addrInfoPtr);
        printf("ResolveFromString() returned unsupported address family!\n");
        return -1;
    }
}

int req_route_info(struct route_info * rtInfo) 
{
	//printf("input from user = %x\n", *rtInfo->dst_node_v4);
	//printf("HNA = %s\n", rtInfo->whereHNA);

	U_CHAR	found;
	char isGW_v4[IPV4_LEN/8];
	char isGW_v6[IPV6_LEN/8];

	bzero(isGW_v4, IPV4_LEN/8);
	bzero(isGW_v6, IPV6_LEN/8);

	struct nList * temp;

	// read HNA file for subnet mask
	char tag[8], subnetAddress[256], buff[1024];
	int	maskLength;
	int	isV4V6MaskLength;

	FILE * fid = fopen(rtInfo->whereHNA, "r");
	if(fid) {
		while(fgets(buff, 1023,fid)){ //parse line
      		sscanf(buff,"%s %s %d",tag,subnetAddress,&maskLength);
      		if(!strcmp(tag,"HNA")){
	
			} else {
				printf("Invalid address string in %s\n", rtInfo->whereHNA); 

			}
		}
		isV4V6MaskLength=resolve_string(subnetAddress);
		//if the version of requested IP address does not match with that of HNA,
		//assign default subnet mask.
		if(isV4V6MaskLength != rtInfo->version_flag) {
			if(rtInfo->version_flag == IS_V4)
				maskLength = IPV4_SUBMASK;
			else if(rtInfo->version_flag == IS_V6)
				maskLength = IPV6_SUBMASK;
		} else {
			maskLength /=8;
		}
	} else {
//		printf("HNA is not specified or file not found: %s\n", rtInfo->whereHNA);
		if(rtInfo->version_flag == IS_V4)
			maskLength = IPV4_SUBMASK;
		else if(rtInfo->version_flag == IS_V6)
			maskLength = IPV6_SUBMASK;
	}

	init_rtInfo(rtInfo);
	nListHeader = nListTail = NULL;  //neighbor list initialization
	oneHopListHeader = oneHopListTail = NULL; //one hop neighbor list initialization
	char dst_str[128];
	bzero(dst_str, 128);

	send_route_req(rtInfo->version_flag);
	read_route_req();
	fill_route_req(rtInfo->version_flag);

//	prtList(rtInfo->version_flag);
	// search and fill the structure

	// create one hop neighbor list and search host
	found = 0;
	temp = nListHeader;
	while(temp != NULL) {
		if(rtInfo->version_flag == IS_V4)
		{
			if(!memcmp(temp->node_v4, rtInfo->dst_node_v4, IPV4_LEN/8 ))
			{
				memcpy(rtInfo->next_hop_v4, temp->gw_v4, IPV4_LEN/8);
				//printf("found host!!\n");
				found = FOUND_HOST;
			}
		} else {
			if(!memcmp(temp->node_v6, rtInfo->dst_node_v6, IPV6_LEN/8 ))
			{
				memcpy(rtInfo->next_hop_v6, temp->gw_v6, IPV6_LEN/8);
				//printf("found host!!\n");
				found = FOUND_HOST;
			}
		}
		if(found)
		{
			rtInfo->error_id = NO_ERROR;
			rtInfo->is_connected = true;
			rtInfo->metric = temp->metric;
		}

		// if gateway equals zero, if one-hop neighbors...
		if(rtInfo->version_flag == IS_V4)
		{
			if( (!memcmp(temp->gw_v4, isGW_v4, IPV4_LEN/8)) && (temp->netmask == IPV4_LEN))
			{
				addOneHopList(temp->node_v4, rtInfo->version_flag);
			}
		} else 
		{
			if( (!memcmp(temp->gw_v6, isGW_v6, IPV6_LEN/8)) && (temp->netmask == IPV6_LEN))
			{
				addOneHopList(temp->node_v4, rtInfo->version_flag);
			}
		}
		temp = temp->next;
	}

	rtInfo->oneHopNeigh = oneHopListHeader;

//	prtOneHopList(rtInfo->version_flag);

	// search subnet next
	if(found)
	{
		mem_free();
		return found;
	} else
	{
		temp = nListHeader;
		while(temp != NULL) {
			if(rtInfo->version_flag == IS_V4)
			{
				//if(!memcmp(temp->node_v4, rtInfo->dst_node_v4, IPV4_SUBMASK ))
				if(!memcmp(temp->node_v4, rtInfo->dst_node_v4, maskLength ))
				{
					rtInfo->error_id = NO_ERROR;
					rtInfo->is_connected = true;
					rtInfo->metric = temp->metric;
					memcpy(rtInfo->next_hop_v4, temp->gw_v4, IPV4_LEN/8);
				//	printf("found subnet!!\n");
					mem_free();
					return FOUND_SUBNET;
				}
			} else 
			{
				printf("3\n");
				//if(!memcmp(temp->node_v6, rtInfo->dst_node_v6, IPV6_SUBMASK ))
				if(!memcmp(temp->node_v6, rtInfo->dst_node_v6, maskLength ))
				{
					rtInfo->error_id = NO_ERROR;
					rtInfo->is_connected = true;
					rtInfo->metric = temp->metric;
					memcpy(rtInfo->next_hop_v6, temp->gw_v6, IPV6_LEN/8);
				//	printf("found subnet!!\n");
					mem_free();
					return FOUND_SUBNET;
				}
			}
			temp = temp->next;
		} 
	}

	rtInfo->error_id = NOT_FOUND;
	mem_free();
	return false;
}
